package com.linkoog.devtools.websocket.toolwindow.left;

import com.intellij.openapi.util.Condition;
import com.intellij.util.ui.ListTableModel;
import com.linkoog.devtools.websocket.model.WSFrameData;

import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import java.util.ArrayList;
import java.util.List;

public class FilteringTableModel<T> extends AbstractTableModel {
    private final ListTableModel<T> myOriginalModel;
    private final List<List<T>> myData = new ArrayList<>();
    private final Class<T> myClz;
    private Condition<? super T> myCondition = null;
    private final ArrayList<Integer> myIndex = new ArrayList<>();

    private final TableModelListener myListDataListener = e -> refilter();

    public FilteringTableModel(ListTableModel<T> originalModel, Class<T> clz) {
        myClz = clz;
        myOriginalModel = originalModel;
        myOriginalModel.addTableModelListener(myListDataListener);
    }

    public void dispose() {
        myOriginalModel.removeTableModelListener(myListDataListener);
    }

    public void setFilter(Condition<? super T> condition) {
        myCondition = condition;
        refilter();
    }

    private void removeAllElements() {
        int index1 = myData.size() - 1;
        if (index1 >= 0) {
            myData.clear();
            fireTableRowsDeleted(0, index1);
        }
        myIndex.clear();
    }

    public void refilter() {
        removeAllElements();
        int count = 0;
        for (int i = 0; i < myOriginalModel.getRowCount(); i++) {
            Object valueAt = myOriginalModel.getRowValue(i);
            boolean include = false;
            if (valueAt != null && myClz.isAssignableFrom(valueAt.getClass())) {
                //noinspection unchecked
                final T element = (T)valueAt;
                if (passElement(element)) {
                    include = true;
                }
            } else {
                include = true;
            }

            if (include) {
                List<T> elements = new ArrayList<>(myOriginalModel.getColumnCount());
                for (int col = 0; col < myOriginalModel.getColumnCount(); col++) {
                    //noinspection unchecked
                    elements.add((T)myOriginalModel.getValueAt(i, col));
                }
                addToFiltered(elements);
                myIndex.add(i);
                count++;
            }
        }

        if (count > 0) {
            fireTableRowsInserted(0, count - 1);
        }
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return myOriginalModel.isCellEditable(myIndex.get(rowIndex), columnIndex);
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        myOriginalModel.setValueAt(aValue, myIndex.get(rowIndex), columnIndex);
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return myOriginalModel.getColumnClass(columnIndex);
    }

    @Override
    public String getColumnName(int column) {
        return myOriginalModel.getColumnName(column);
    }

    protected void addToFiltered(List<T> elt) {
        myData.add(elt);
    }

    public int getSize() {
        return myData.size();
    }

    private boolean passElement(T element) {
        return myCondition == null || myCondition.value(element);
    }

    @Override
    public int getRowCount() {
        return myData.size();
    }

    @Override
    public int getColumnCount() {
        return myOriginalModel.getColumnCount();
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        if (rowIndex >= myData.size() || rowIndex < 0 || columnIndex < 0 || columnIndex >= getColumnCount()) {
            return null;
        }
        return myData.get(rowIndex).get(columnIndex);
    }

    public ListTableModel<T> getOriginalModel() {
        return myOriginalModel;
    }

    public int getOriginalIndex(int idx) {
        return idx < 0 ? idx : idx >= myIndex.size() ? idx : myIndex.get(idx);
    }
}
